1 package org.apache.lucene.util;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 import java.util.ArrayList;
21
22
23
24
25
26
27 public final class GeoUtils {
28 public static final short BITS = 31;
29 private static final double LON_SCALE = (0x1L<<BITS)/360.0D;
30 private static final double LAT_SCALE = (0x1L<<BITS)/180.0D;
31 public static final double TOLERANCE = 1E-6;
32
33
34 public static final double MIN_LON_INCL = -180.0D;
35
36
37 public static final double MAX_LON_INCL = 180.0D;
38
39
40 public static final double MIN_LAT_INCL = -90.0D;
41
42
43 public static final double MAX_LAT_INCL = 90.0D;
44
45
46 private GeoUtils() {
47 }
48
49 public static final Long mortonHash(final double lon, final double lat) {
50 return BitUtil.interleave(scaleLon(lon), scaleLat(lat));
51 }
52
53 public static final double mortonUnhashLon(final long hash) {
54 return unscaleLon(BitUtil.deinterleave(hash));
55 }
56
57 public static final double mortonUnhashLat(final long hash) {
58 return unscaleLat(BitUtil.deinterleave(hash >>> 1));
59 }
60
61 private static final long scaleLon(final double val) {
62 return (long) ((val-MIN_LON_INCL) * LON_SCALE);
63 }
64
65 private static final long scaleLat(final double val) {
66 return (long) ((val-MIN_LAT_INCL) * LAT_SCALE);
67 }
68
69 private static final double unscaleLon(final long val) {
70 return (val / LON_SCALE) + MIN_LON_INCL;
71 }
72
73 private static final double unscaleLat(final long val) {
74 return (val / LAT_SCALE) + MIN_LAT_INCL;
75 }
76
77 public static double compare(final double v1, final double v2) {
78 final double delta = v1-v2;
79 return Math.abs(delta) <= TOLERANCE ? 0 : delta;
80 }
81
82
83
84
85 public static double normalizeLon(double lon_deg) {
86 if (lon_deg >= -180 && lon_deg <= 180) {
87 return lon_deg;
88 }
89 double off = (lon_deg + 180) % 360;
90 if (off < 0) {
91 return 180 + off;
92 } else if (off == 0 && lon_deg > 0) {
93 return 180;
94 } else {
95 return -180 + off;
96 }
97 }
98
99
100
101
102 public static double normalizeLat(double lat_deg) {
103 if (lat_deg >= -90 && lat_deg <= 90) {
104 return lat_deg;
105 }
106 double off = Math.abs((lat_deg + 90) % 360);
107 return (off <= 180 ? off : 360-off) - 90;
108 }
109
110
111
112
113
114
115 public static boolean bboxContains(final double lon, final double lat, final double minLon,
116 final double minLat, final double maxLon, final double maxLat) {
117 return (compare(lon, minLon) >= 0 && compare(lon, maxLon) <= 0
118 && compare(lat, minLat) >= 0 && compare(lat, maxLat) <= 0);
119 }
120
121
122
123
124
125
126
127
128
129
130 public static boolean pointInPolygon(double[] x, double[] y, double lat, double lon) {
131 assert x.length == y.length;
132 boolean inPoly = false;
133
134
135
136
137
138 for (int i = 1; i < x.length; i++) {
139 if (x[i] < lon && x[i-1] >= lon || x[i-1] < lon && x[i] >= lon) {
140 if (y[i] + (lon - x[i]) / (x[i-1] - x[i]) * (y[i-1] - y[i]) < lat) {
141 inPoly = !inPoly;
142 }
143 }
144 }
145 return inPoly;
146 }
147
148 public static String geoTermToString(long term) {
149 StringBuilder s = new StringBuilder(64);
150 final int numberOfLeadingZeros = Long.numberOfLeadingZeros(term);
151 for (int i = 0; i < numberOfLeadingZeros; i++) {
152 s.append('0');
153 }
154 if (term != 0) {
155 s.append(Long.toBinaryString(term));
156 }
157 return s.toString();
158 }
159
160
161 public static boolean rectDisjoint(final double aMinX, final double aMinY, final double aMaxX, final double aMaxY,
162 final double bMinX, final double bMinY, final double bMaxX, final double bMaxY) {
163 return (aMaxX < bMinX || aMinX > bMaxX || aMaxY < bMinY || aMinY > bMaxY);
164 }
165
166
167
168
169 public static boolean rectWithin(final double aMinX, final double aMinY, final double aMaxX, final double aMaxY,
170 final double bMinX, final double bMinY, final double bMaxX, final double bMaxY) {
171 return !(aMinX < bMinX || aMinY < bMinY || aMaxX > bMaxX || aMaxY > bMaxY);
172 }
173
174 public static boolean rectCrosses(final double aMinX, final double aMinY, final double aMaxX, final double aMaxY,
175 final double bMinX, final double bMinY, final double bMaxX, final double bMaxY) {
176 return !(rectDisjoint(aMinX, aMinY, aMaxX, aMaxY, bMinX, bMinY, bMaxX, bMaxY) ||
177 rectWithin(aMinX, aMinY, aMaxX, aMaxY, bMinX, bMinY, bMaxX, bMaxY));
178 }
179
180
181
182
183 public static boolean rectContains(final double aMinX, final double aMinY, final double aMaxX, final double aMaxY,
184 final double bMinX, final double bMinY, final double bMaxX, final double bMaxY) {
185 return !(bMinX < aMinX || bMinY < aMinY || bMaxX > aMaxX || bMaxY > aMaxY);
186 }
187
188
189
190
191 public static boolean rectIntersects(final double aMinX, final double aMinY, final double aMaxX, final double aMaxY,
192 final double bMinX, final double bMinY, final double bMaxX, final double bMaxY) {
193 return !((aMaxX < bMinX || aMinX > bMaxX || aMaxY < bMinY || aMinY > bMaxY) );
194 }
195
196
197
198
199 public static boolean rectCrossesPoly(final double rMinX, final double rMinY, final double rMaxX,
200 final double rMaxY, final double[] shapeX, final double[] shapeY,
201 final double sMinX, final double sMinY, final double sMaxX,
202 final double sMaxY) {
203
204 if (rectDisjoint(rMinX, rMinY, rMaxX, rMaxY, sMinX, sMinY, sMaxX, sMaxY)) {
205 return false;
206 }
207
208 final double[][] bbox = new double[][] { {rMinX, rMinY}, {rMaxX, rMinY}, {rMaxX, rMaxY}, {rMinX, rMaxY}, {rMinX, rMinY} };
209 final int polyLength = shapeX.length-1;
210 double d, s, t, a1, b1, c1, a2, b2, c2;
211 double x00, y00, x01, y01, x10, y10, x11, y11;
212
213
214 for (short b=0; b<4; ++b) {
215 a1 = bbox[b+1][1]-bbox[b][1];
216 b1 = bbox[b][0]-bbox[b+1][0];
217 c1 = a1*bbox[b+1][0] + b1*bbox[b+1][1];
218 for (int p=0; p<polyLength; ++p) {
219 a2 = shapeY[p+1]-shapeY[p];
220 b2 = shapeX[p]-shapeX[p+1];
221
222 d = a1*b2 - a2*b1;
223 if (d != 0) {
224
225 c2 = a2*shapeX[p+1] + b2*shapeY[p+1];
226 s = (1/d)*(b2*c1 - b1*c2);
227 t = (1/d)*(a1*c2 - a2*c1);
228 x00 = StrictMath.min(bbox[b][0], bbox[b+1][0]) - TOLERANCE;
229 x01 = StrictMath.max(bbox[b][0], bbox[b+1][0]) + TOLERANCE;
230 y00 = StrictMath.min(bbox[b][1], bbox[b+1][1]) - TOLERANCE;
231 y01 = StrictMath.max(bbox[b][1], bbox[b+1][1]) + TOLERANCE;
232 x10 = StrictMath.min(shapeX[p], shapeX[p+1]) - TOLERANCE;
233 x11 = StrictMath.max(shapeX[p], shapeX[p+1]) + TOLERANCE;
234 y10 = StrictMath.min(shapeY[p], shapeY[p+1]) - TOLERANCE;
235 y11 = StrictMath.max(shapeY[p], shapeY[p+1]) + TOLERANCE;
236
237 boolean touching = ((x00 == s && y00 == t) || (x01 == s && y01 == t))
238 || ((x10 == s && y10 == t) || (x11 == s && y11 == t));
239
240 if (!(touching || x00 > s || x01 < s || y00 > t || y01 < t || x10 > s || x11 < s || y10 > t || y11 < t)) {
241 return true;
242 }
243 }
244 }
245 }
246 return false;
247 }
248
249 public static boolean lineCrossesPoly(double x1, double y1, double x2, double y2, double[] shapeX, double[] shapeY, final double sMinX, final double sMinY, final double sMaxX,
250 final double sMaxY) {
251 final double a1 = y2 - y1;
252 final double b1 = x2 - x1;
253 final double c1 = a1*x2 + b1*y2;
254 final int polyLength = shapeX.length;
255 double a2, b2, c2, s, t, d;
256 double x00, x01, y00, y01, x10, x11, y10, y11;
257 for (int p=0; p<polyLength; ++p) {
258 a2 = shapeY[p+1]-shapeY[p];
259 b2 = shapeX[p]-shapeX[p+1];
260
261 d = a1*b2 - a2*b1;
262 if (d != 0) {
263
264 c2 = a2*shapeX[p+1] + b2*shapeY[p+1];
265 s = (1/d)*(b2*c1 - b1*c2);
266 t = (1/d)*(a1*c2 - a2*c1);
267 x00 = StrictMath.min(x1, x2) - TOLERANCE;
268 x01 = StrictMath.max(x1, x2) + TOLERANCE;
269 y00 = StrictMath.min(y1, y2) - TOLERANCE;
270 y01 = StrictMath.max(y1, y2) + TOLERANCE;
271 x10 = StrictMath.min(shapeX[p], shapeX[p+1]) - TOLERANCE;
272 x11 = StrictMath.max(shapeX[p], shapeX[p+1]) + TOLERANCE;
273 y10 = StrictMath.min(shapeY[p], shapeY[p+1]) - TOLERANCE;
274 y11 = StrictMath.max(shapeY[p], shapeY[p+1]) + TOLERANCE;
275
276 boolean touching = ((x00 == s && y00 == t) || (x01 == s && y01 == t))
277 || ((x10 == s && y10 == t) || (x11 == s && y11 == t));
278
279 if (!(touching || x00 > s || x01 < s || y00 > t || y01 < t || x10 > s || x11 < s || y10 > t || y11 < t)) {
280 return true;
281 }
282 }
283 }
284 return false;
285 }
286
287
288
289
290
291
292
293
294
295 @SuppressWarnings({"unchecked","rawtypes"})
296 public static ArrayList<double[]> circleToPoly(final double lon, final double lat, final double radiusMeters) {
297 double angle;
298
299 final int sides = 25;
300 ArrayList<double[]> geometry = new ArrayList();
301 double[] lons = new double[sides];
302 double[] lats = new double[sides];
303
304 double[] pt = new double[2];
305 final int sidesLen = sides-1;
306 for (int i=0; i<sidesLen; ++i) {
307 angle = (i*360/sides);
308 pt = GeoProjectionUtils.pointFromLonLatBearing(lon, lat, angle, radiusMeters, pt);
309 lons[i] = pt[0];
310 lats[i] = pt[1];
311 }
312
313 lons[sidesLen] = lons[0];
314 lats[sidesLen] = lats[0];
315 geometry.add(lons);
316 geometry.add(lats);
317
318 return geometry;
319 }
320
321
322
323
324 public static boolean rectWithinPoly(final double rMinX, final double rMinY, final double rMaxX, final double rMaxY,
325 final double[] shapeX, final double[] shapeY, final double sMinX,
326 final double sMinY, final double sMaxX, final double sMaxY) {
327
328
329 return !(rectCrossesPoly(rMinX, rMinY, rMaxX, rMaxY, shapeX, shapeY, sMinX, sMinY, sMaxX, sMaxY) ||
330 !pointInPolygon(shapeX, shapeY, rMinY, rMinX) || !pointInPolygon(shapeX, shapeY, rMinY, rMaxX) ||
331 !pointInPolygon(shapeX, shapeY, rMaxY, rMaxX) || !pointInPolygon(shapeX, shapeY, rMaxY, rMinX));
332 }
333
334 private static boolean rectAnyCornersOutsideCircle(final double rMinX, final double rMinY, final double rMaxX, final double rMaxY,
335 final double centerLon, final double centerLat, final double radiusMeters) {
336 return SloppyMath.haversin(centerLat, centerLon, rMinY, rMinX)*1000.0 > radiusMeters
337 || SloppyMath.haversin(centerLat, centerLon, rMaxY, rMinX)*1000.0 > radiusMeters
338 || SloppyMath.haversin(centerLat, centerLon, rMaxY, rMaxX)*1000.0 > radiusMeters
339 || SloppyMath.haversin(centerLat, centerLon, rMinY, rMaxX)*1000.0 > radiusMeters;
340 }
341
342 private static boolean rectAnyCornersInCircle(final double rMinX, final double rMinY, final double rMaxX, final double rMaxY,
343 final double centerLon, final double centerLat, final double radiusMeters) {
344 return SloppyMath.haversin(centerLat, centerLon, rMinY, rMinX)*1000.0 <= radiusMeters
345 || SloppyMath.haversin(centerLat, centerLon, rMaxY, rMinX)*1000.0 <= radiusMeters
346 || SloppyMath.haversin(centerLat, centerLon, rMaxY, rMaxX)*1000.0 <= radiusMeters
347 || SloppyMath.haversin(centerLat, centerLon, rMinY, rMaxX)*1000.0 <= radiusMeters;
348 }
349
350 public static boolean rectWithinCircle(final double rMinX, final double rMinY, final double rMaxX, final double rMaxY,
351 final double centerLon, final double centerLat, final double radiusMeters) {
352 return rectAnyCornersOutsideCircle(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, radiusMeters) == false;
353 }
354
355
356
357
358
359
360 public static boolean rectCrossesCircle(final double rMinX, final double rMinY, final double rMaxX, final double rMaxY,
361 final double centerLon, final double centerLat, final double radiusMeters) {
362 return rectAnyCornersInCircle(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, radiusMeters)
363 || isClosestPointOnRectWithinRange(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, radiusMeters);
364 }
365
366 private static boolean isClosestPointOnRectWithinRange(final double rMinX, final double rMinY, final double rMaxX, final double rMaxY,
367 final double centerLon, final double centerLat, final double radiusMeters) {
368 double[] closestPt = {0, 0};
369 GeoDistanceUtils.closestPointOnBBox(rMinX, rMinY, rMaxX, rMaxY, centerLon, centerLat, closestPt);
370 return SloppyMath.haversin(centerLat, centerLon, closestPt[1], closestPt[0])*1000.0 <= radiusMeters;
371 }
372
373
374
375
376 public static GeoRect circleToBBox(final double centerLon, final double centerLat, final double radiusMeters) {
377 final double radLat = StrictMath.toRadians(centerLat);
378 final double radLon = StrictMath.toRadians(centerLon);
379 double radDistance = radiusMeters / GeoProjectionUtils.SEMIMAJOR_AXIS;
380 double minLat = radLat - radDistance;
381 double maxLat = radLat + radDistance;
382 double minLon;
383 double maxLon;
384
385 if (minLat > GeoProjectionUtils.MIN_LAT_RADIANS && maxLat < GeoProjectionUtils.MAX_LAT_RADIANS) {
386 double deltaLon = StrictMath.asin(StrictMath.sin(radDistance) / StrictMath.cos(radLat));
387 minLon = radLon - deltaLon;
388 if (minLon < GeoProjectionUtils.MIN_LON_RADIANS) {
389 minLon += 2d * StrictMath.PI;
390 }
391 maxLon = radLon + deltaLon;
392 if (maxLon > GeoProjectionUtils.MAX_LON_RADIANS) {
393 maxLon -= 2d * StrictMath.PI;
394 }
395 } else {
396
397 minLat = StrictMath.max(minLat, GeoProjectionUtils.MIN_LAT_RADIANS);
398 maxLat = StrictMath.min(maxLat, GeoProjectionUtils.MAX_LAT_RADIANS);
399 minLon = GeoProjectionUtils.MIN_LON_RADIANS;
400 maxLon = GeoProjectionUtils.MAX_LON_RADIANS;
401 }
402
403 return new GeoRect(StrictMath.toDegrees(minLon), StrictMath.toDegrees(maxLon),
404 StrictMath.toDegrees(minLat), StrictMath.toDegrees(maxLat));
405 }
406
407 public static GeoRect polyToBBox(double[] polyLons, double[] polyLats) {
408 if (polyLons.length != polyLats.length) {
409 throw new IllegalArgumentException("polyLons and polyLats must be equal length");
410 }
411
412 double minLon = Double.POSITIVE_INFINITY;
413 double maxLon = Double.NEGATIVE_INFINITY;
414 double minLat = Double.POSITIVE_INFINITY;
415 double maxLat = Double.NEGATIVE_INFINITY;
416
417 for (int i=0;i<polyLats.length;i++) {
418 if (GeoUtils.isValidLon(polyLons[i]) == false) {
419 throw new IllegalArgumentException("invalid polyLons[" + i + "]=" + polyLons[i]);
420 }
421 if (GeoUtils.isValidLat(polyLats[i]) == false) {
422 throw new IllegalArgumentException("invalid polyLats[" + i + "]=" + polyLats[i]);
423 }
424 minLon = Math.min(polyLons[i], minLon);
425 maxLon = Math.max(polyLons[i], maxLon);
426 minLat = Math.min(polyLats[i], minLat);
427 maxLat = Math.max(polyLats[i], maxLat);
428 }
429
430 return new GeoRect(GeoUtils.unscaleLon(GeoUtils.scaleLon(minLon)), GeoUtils.unscaleLon(GeoUtils.scaleLon(maxLon)),
431 GeoUtils.unscaleLat(GeoUtils.scaleLat(minLat)), GeoUtils.unscaleLat(GeoUtils.scaleLat(maxLat)));
432 }
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488 public static boolean isValidLat(double lat) {
489 return Double.isNaN(lat) == false && lat >= MIN_LAT_INCL && lat <= MAX_LAT_INCL;
490 }
491
492 public static boolean isValidLon(double lon) {
493 return Double.isNaN(lon) == false && lon >= MIN_LON_INCL && lon <= MAX_LON_INCL;
494 }
495 }